package org.hawk.gwt.ppc.parser.impl;

import java.io.Reader;

import org.hawk.gwt.ppc.parser.CompilationUnitDescriptor;
import org.hawk.gwt.ppc.parser.Descriptor;
import org.hawk.gwt.ppc.parser.JavaParser;
import org.hawk.gwt.ppc.parser.PreprocessorParseException;
import org.hawk.gwt.ppc.parser.Token;
import org.hawk.gwt.ppc.utils.PreprocessorException;

/**
 * Implementation for {@link JavaParser}
 * 
 * @author alex.bereznevatiy@gmail.com
 *
 */
public class TokenBasedJavaParser extends JavaParser {
	
	private Reader reader;

	/**
	 * Creates implementation class of {@link JavaParser}.
	 * @param reader to read tokens
	 */
	public TokenBasedJavaParser(Reader reader) {
		this.reader = reader;
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.parser.JavaParser#parse()
	 */
	public CompilationUnitDescriptor parse() throws PreprocessorException {
		Tokenizer tokenizer = new Tokenizer(reader);
		
		if (!tokenizer.hasNext()) {
			throw new PreprocessorParseException("Unexpected end of file", -1, -1);
		}
		tokenizer.next();
		
		return (CompilationUnitDescriptor) expect(tokenizer, DescriptorType.COMPILATION_UNIT);
	}
	
	/**
	 * Expect that {@link Tokenizer} returns one of the passed types.
	 * In other case PreprocessorParseException is raised.
	 * @param tokenizer
	 * @param expectedTypes
	 * @return next descriptor
	 */
	Descriptor expect(Tokenizer tokenizer, DescriptorType...expectedTypes) throws PreprocessorException {
		Token token = tokenizer.current();
		if (tokenizer.isEof()) {
			return handleEOF(tokenizer, expectedTypes);
		}
		
		for (DescriptorType type : expectedTypes) {
			if (!type.equals(DescriptorType.EOF) && type.accepts(token)) {
				return type.parse(this, tokenizer);
			}
		}
		
		throw new PreprocessorParseException("Unexpected token", tokenizer.current());
	}

	
	/**
	 * Expect that {@link Tokenizer} returns one of the passed types or comment.
	 * In other case PreprocessorParseException is raised.
	 * @param tokenizer
	 * @param expectedTypes
	 * @return next descriptor
	 */
	Descriptor expectTypeOrComment(Tokenizer tokenizer, DescriptorType...expectedTypes) throws PreprocessorException {
		Token token = tokenizer.current();
		if (tokenizer.isEof()) {
			return handleEOF(tokenizer, expectedTypes);
		}
		
		if (DescriptorType.COMMENT.accepts(token)) {
			return DescriptorType.COMMENT.parse(this, tokenizer);
		}
		return expect(tokenizer, expectedTypes);
	}

	private Descriptor handleEOF(Tokenizer tokenizer,
			DescriptorType... expectedTypes) throws PreprocessorParseException {
		for (DescriptorType type : expectedTypes) {
			if (type.equals(DescriptorType.EOF)) {
				return null;
			}
		}
		throw new PreprocessorParseException("Unexpected end of file", tokenizer.current());
	}
}
